﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.IO;

namespace AjaxUploadServer
{
	public interface IServerManager
	{
		string MapPath(string name);
		string GetHttpMethod();
		void ResponseContentType(string contentType);
		void ResponseCharSet(string charset);
		void ResponseHeader(string name, string value);
		void ResponseCrossAllow();
		void Response(object output);
		void ResponseEnd();

		string RequestOrigin();
		string RequestHeader(string name);
		string RequestForm(string key);
		Stream RequestStream(bool buffered);
		Stream RequestStream();
		HttpFileCollection RequestFiles();
	}
	class ServerManager : IServerManager
	{
		public ServerManager(HttpRequest request, HttpResponse response)
		{
			this.request = request;
			this.response = response;
		}
		public ServerManager() : this(HttpContext.Current.Request, HttpContext.Current.Response)
		{
		}
		private readonly HttpRequest request;
		private readonly HttpResponse response;

		public string MapPath(string name)
		{
			return this.request.RequestContext.HttpContext.Server.MapPath(name);
		}
		public string GetHttpMethod()
		{
			return this.request.HttpMethod.ToUpper();
		}

		public void ResponseContentType(string contentType)
		{
			this.response.ContentType = contentType;
		}
		public void ResponseCharSet(string charset)
		{
			this.response.Charset = charset;
		}
		public void Response(object output)
		{
			this.response.Write(output);
		}
		public void ResponseEnd()
		{
			this.response.End();
		}
		public void ResponseHeader(string name, string value)
		{
			if (String.IsNullOrEmpty(name) || String.IsNullOrEmpty(value)) return;
			response.AppendHeader(name, value);
		}
		public void ResponseCrossAllow()
		{
			var origin = RequestOrigin();
			ResponseHeader("Access-Control-Allow-Origin", origin);
		}
		public string RequestOrigin()
		{
			return request.Headers["Origin"];
		}
		public string RequestForm(string key)
		{
			return request.Form[key];
		}
		public string RequestHeader(string name)
		{
			return request.Headers[name];
		}
		public Stream RequestStream(bool buffered)
		{
			return buffered ? request.GetBufferedInputStream() : request.GetBufferlessInputStream();
		}
		public Stream RequestStream()
		{
			return RequestStream(false);
		}
		public HttpFileCollection RequestFiles()
		{
			return request.Files;
		}
	}

	public class UploadServer
	{

		internal delegate ushort CommandMethod(ServerResponser output);

		public UploadServer(IServerManager server)
		{
			this.server = server;
			this.methods = new Dictionary<ushort, CommandMethod>();
			this._code = ServerCode.CreateFromConfigure("ServerCodeFile", this.server);
			this._conf = ServerConfigure.CreateFromConfigure("ConfigureFile", this.server);
			this._files = new ServerFileManage(server);

			object cmd, data, session, auth;
			if (_conf.Configure.TryGetValue("CMDFIELD", out cmd) && cmd != null)
			{
				_cmdfield = cmd.ToString();
			}
			else
			{
				_cmdfield = "uploadcmd";
			}
			if (_conf.Configure.TryGetValue("DATAFIELD", out data) && data != null)
			{
				_datafield = data.ToString();
			}
			else
			{
				_datafield = "data";
			}
			if (_conf.Configure.TryGetValue("SESSIONFIELD", out session) && session != null)
			{
				_sessionfield = session.ToString();
			}
			else
			{
				_sessionfield = "session";
			}
			if (_conf.Configure.TryGetValue("AUTHFIELD", out auth) && auth != null)
			{
				_authfield = auth.ToString();
			}
			else
			{
				_authfield = "authcode";
			}

			MethodInit();
		}

		private static string NewId()
		{
			return Guid.NewGuid().ToString("N").ToLower();
		}

		private static Random RndSeed = new Random(new Random().Next(int.MinValue, int.MaxValue));
		private static string NewSession()
		{
			byte[] buffer = new byte[16];
			RndSeed.NextBytes(buffer);
			return BitConverter.ToString(buffer).Replace("-", String.Empty).ToLower() + NewId();
		}

		private readonly IServerManager server;
		private readonly ServerCode _code;
		private readonly ServerConfigure _conf;
		private readonly string _cmdfield;
		private readonly string _datafield;
		private readonly string _sessionfield;
		private readonly string _authfield;
		private readonly Dictionary<ushort, CommandMethod> methods;
		private readonly ServerFileManage _files;

		/// <summary>
		/// 是否需要请求方提供内容HASH
		/// </summary>
		private bool HashRequire
		{
			get
			{
				short hash;
				if (!_conf.Configure.TryParse<short>("HASH", out hash))
				{
					hash = 1;
				}
				return hash > 0;
			}
		}

		/// <summary>
		/// 是否需要请求方进行身份验证
		/// </summary>
		private bool AuthRequire
		{
			get
			{
				short auth;
				if (!_conf.Configure.TryParse<short>("AUTH", out auth))
				{
					auth = 1;
				}
				return auth > 0;
			}
		}


		private bool IsPost()
		{
			return this.server.GetHttpMethod() == "POST";
		}
		private ushort GetCommandValue(string key)
		{
			if (!_conf.Commands.ContainsKey(key)) return 0;
			return _conf.Commands[key];
		}
		private ushort GetClientCommand()
		{
			ushort cmd;
			var val = server.RequestForm(_cmdfield);
			if(val == null || !ushort.TryParse(val.ToString(), out cmd))
			{
				cmd = ushort.MinValue;
			}
			return cmd;
		}

		private void MethodRegist(string cmd, CommandMethod method)
		{
			methods[GetCommandValue(cmd)] = method;
		}

		private ushort CMD_UNKNOWN(ServerResponser output)
		{
			return 10000;
		}
		private ushort CMD_NONE(ServerResponser output)
		{
			output.Set("cmd", _cmdfield);
			output.Set("next", GetCommandValue("HELP"));
			return 0;
		}

		private void MethodInvoke(ushort cmd, ServerResponser output)
		{
			if (methods.ContainsKey(cmd))
			{
				output.status = methods[cmd](output);
			}
			else
			{
				output.status = CMD_UNKNOWN(output);
			}
		}

		public void Output()
		{
			var output = new ServerResponser(0);
			server.ResponseCrossAllow();
			server.ResponseCharSet(Encoding.UTF8.BodyName);
			server.ResponseContentType("text/json");

			if (IsPost())
			{
				MethodInvoke(GetClientCommand(), output);
			}
			else
			{
				output.status = CMD_NONE(output);
			}

			output.ReadCode(_code);
			server.Response(output);
			server.ResponseEnd();
		}

		private string SaveSession(string session)
		{
			if (!String.IsNullOrEmpty(session))
			{
				var filename = String.Format("session/{0}.txt", session);
				filename = server.MapPath(filename);
				FileInfo fi = new FileInfo(filename);
				if (fi.Exists)
				{
					//fi.MoveTo(nfi.FullName);
					return session;
				}
			}

			var newssid = NewSession();
			var newfile = String.Format("session/{0}.txt", newssid);
			newfile = server.MapPath(newfile);
			FileInfo nfi = new FileInfo(newfile);
			if (!nfi.Directory.Exists) nfi.Directory.Create();

			

			using (StreamWriter writer = new StreamWriter(nfi.OpenWrite()))
			{
				writer.Write('@');
				writer.Close();
			}
			return newssid;
		}

		private bool SessionExists(string session)
		{
			var filename = String.Format("session/{0}.txt", session);
			filename = server.MapPath(filename);
			return File.Exists(filename);
		}

		private ushort WriteSession(string line)
		{
			var session = server.RequestForm(_sessionfield);
			var filename = String.Format("session/{0}.txt", session);
			filename = server.MapPath(filename);
			using (var writer = new StreamWriter(File.Open(filename, FileMode.Append, FileAccess.Write, FileShare.Read), Encoding.UTF8))
			{
				//writer.BaseStream.Seek(0, SeekOrigin.End);
				writer.WriteLine();
				writer.Write(line);
				//writer.Write(line);
				writer.Close();
			}
			return 200;
		}

		private bool RequestCheck(ServerResponser output)
		{
			var session = server.RequestForm(_sessionfield);
			if (!SessionExists(session))
			{
				output.status = 10002;
				return false;
			}
			if (AuthRequire)
			{
				output.status = 10003;
				return false;
			}
			return true;
		}

		private void MethodInit()
		{
			MethodRegist("NONE", CMD_NONE);

			MethodRegist("HELP", delegate (ServerResponser output)
			{
				output.Set("cmd", _conf.Commands);
				output.Set("next", GetCommandValue("HELO"));
				return 200;
			});

			MethodRegist("HELO", delegate (ServerResponser output)
			{
				var session = SaveSession(server.RequestForm(_sessionfield));
				var next = AuthRequire ? GetCommandValue("AUTH") : GetCommandValue("HELO");
				output.Set("next", next);
				output.Set(_sessionfield, session);
				return 200;
			});

			MethodRegist("CONF", delegate (ServerResponser output)
			{
				if (!RequestCheck(output))
				{
					return output.status;
				}
				output.Set("config", _conf.Configure);
				return 200;
			});

			MethodRegist("SHND", delegate (ServerResponser output)
			{
				if (!RequestCheck(output))
				{
					return output.status;
				}

				var json = server.RequestForm(_datafield);
				IServerFile file;
				try
				{
					file = _files.CreateFile(json);
				}
				catch
				{
					return 11000;
				}
				if (file.ClientFileInfo.IsEmpty)
				{
					return 11001;
				}
				if (HashRequire && String.IsNullOrEmpty(file.ClientFileInfo.hash))
				{
					return 11002;
				}

				if (!file.SaveInfo())
				{
					return 11003;
				}

				output["guid"] = file.Id;
				output["next"] = _conf.Commands["UPLO"];
				return WriteSession(file.Id);
			});
		}

	}

	public struct ServerResponser
	{
		public ServerResponser(ushort status)
		{
			this.hash = new Hashtable();
			this["status"] = status;
		}

		private Hashtable hash;

		public bool Has(string key)
		{
			return hash.ContainsKey(key);
		}

		public void Set(string key, object value)
		{
			hash[key] = value;
		}
		public object Get(string key)
		{
			return Has(key) ? hash[key] : null;
		}

		public T Get<T>(string key)
		{
			if (!Has(key)) return default(T);
			var val = hash[key];
			if (val is T) return (T)val;
			return default(T);
		}

		public object this[string key]
		{
			get { return Get(key); }
			set { Set(key, value); }
		}
		

		public override string ToString()
		{
			var h = new Hashtable();
			foreach(var key in hash.Keys)
			{
				if (hash[key] == null) continue;
				h[key] = hash[key];
			}
			return Newtonsoft.Json.JsonConvert.SerializeObject(h);
		}

		public ushort status
		{
			get
			{
				return Get<ushort>("status");
			}
			set
			{
				hash["status"] = value;
			}
		}

		public void ReadCode(ServerCode code)
		{
			var item = code.GetItem(status);
			if (item.IsEmpty)
			{
				hash["message"] = null;
			}
			else
			{
				hash["message"] = item.message;
			}
		}
	}
}